package com.peas.platform.common.core.configuration;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.context.request.RequestContextListener;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.*;

import javax.annotation.Resource;
import javax.servlet.MultipartConfigElement;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

/**
 * @author caizhh
 */
@Configuration
@Slf4j
public class WebConfig implements WebMvcConfigurer {

    @Value("${spring.jackson.date-format:}")
    private String dateFormat;
    @Value("${spring.profiles.active:local}")
    private String profile;

    @Resource
    private ApplicationContext ac;

    private CorsConfiguration corsConfig() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.addAllowedOriginPattern("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfig());
        return new CorsFilter(source);
    }

    @Bean
    public RequestContextListener requestContextListener() {
        log.debug("WebConfig requestContextListener");
        return new RequestContextListener();
    }

    @Bean
    public MultipartConfigElement multipartConfigElement() {
        MultipartConfigFactory factory = new MultipartConfigFactory();
        if (!StrUtil.equals("local", profile)) {
            String location = System.getProperty("user.home") + "/data/tmp";
            File tmpFile = new File(location);
            if (!tmpFile.exists()) {
                boolean bool = tmpFile.mkdirs();
                log.trace(String.valueOf(bool));
            }
            factory.setLocation(location);
        }
        return factory.createMultipartConfig();
    }

    @Bean
    public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
        return jacksonObjectMapperBuilder -> {
            jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
            jacksonObjectMapperBuilder.featuresToDisable(SerializationFeature.FAIL_ON_SELF_REFERENCES);
            jacksonObjectMapperBuilder.failOnEmptyBeans(false);
            jacksonObjectMapperBuilder.failOnUnknownProperties(false);
        };
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        String pattern = DatePattern.NORM_DATETIME_PATTERN;
        if (StrUtil.isNotBlank(dateFormat)) {
            pattern = dateFormat;
        }
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        mapper.disable(SerializationFeature.FAIL_ON_SELF_REFERENCES);
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.setDateFormat(new SimpleDateFormat(pattern));
        mapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        Module module = javaTimeModule();
        mapper.registerModule(module);
        return mapper;
    }

    private Module javaTimeModule() {
        JavaTimeModule module = new JavaTimeModule();
        //输出Long的时候转成String，解决js的double丢失精度问题
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        //日期序列化
        module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        module.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        module.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        //日期反序列化
        module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        module.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        module.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        module.addDeserializer(BigDecimal.class, new JsonDeserializer<BigDecimal>() {
            @Override
            public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
                return new ConverterBigDecimal().convert(p.getText());
            }
        });
        module.addDeserializer(Boolean.class, new JsonDeserializer<Boolean>() {
            @Override
            public Boolean deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
                return new ConverterBoolean().convert(p.getText());
            }
        });
        module.addDeserializer(Date.class, new JsonDeserializer<Date>() {
            @Override
            public Date deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
                return new ConverterDate().convert(p.getText());
            }
        });
        module.addDeserializer(Integer.class, new JsonDeserializer<Integer>() {
            @Override
            public Integer deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
                return new ConverterInteger().convert(p.getText());
            }
        });
        module.addDeserializer(Long.class, new JsonDeserializer<Long>() {
            @Override
            public Long deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
                return new ConverterLong().convert(p.getText());
            }
        });
        module.addDeserializer(Time.class, new JsonDeserializer<Time>() {
            @Override
            public Time deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
                return new ConverterTime().convert(p.getText());
            }
        });
        module.addDeserializer(Timestamp.class, new JsonDeserializer<Timestamp>() {
            @Override
            public Timestamp deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
                return new ConverterTimestamp().convert(p.getText());
            }
        });
        return module;
    }

    private MappingJackson2HttpMessageConverter getStringHttpMessageConverter() {
        MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        List<MediaType> supportedMediaTypes = new ArrayList<>();
        supportedMediaTypes.add(MediaType.APPLICATION_JSON);
        supportedMediaTypes.add(new MediaType("application", "json", StandardCharsets.UTF_8));
        supportedMediaTypes.add(MediaType.APPLICATION_PROBLEM_JSON);
        supportedMediaTypes.add(new MediaType("application", "problem+json", StandardCharsets.UTF_8));
        mappingJackson2HttpMessageConverter.setSupportedMediaTypes(supportedMediaTypes);
        ObjectMapper mapper = objectMapper();
        mappingJackson2HttpMessageConverter.setObjectMapper(mapper);
        return mappingJackson2HttpMessageConverter;
    }

    @Override
    public void configurePathMatch(@Nullable PathMatchConfigurer configurer) {
        log.debug("WebConfig configurePathMatch");
    }

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        log.debug("WebConfig configureContentNegotiation");
        configurer.defaultContentType(new MediaType(MediaType.APPLICATION_JSON, StandardCharsets.UTF_8));
    }

    @Override
    public void configureAsyncSupport(@Nullable AsyncSupportConfigurer configurer) {
        log.debug("WebConfig configureAsyncSupport");
    }

    @Override
    public void configureDefaultServletHandling(@Nullable DefaultServletHandlerConfigurer configurer) {
        log.debug("WebConfig configureDefaultServletHandling");
    }

    @Override
    public void addFormatters(FormatterRegistry registry) {
        log.debug("WebConfig addFormatters");
        registry.addFormatter(new FormatterDate());
        registry.addConverter(new ConverterTimestamp());
        registry.addConverter(new ConverterDate());
        registry.addConverter(new ConverterTime());
        registry.addConverter(new ConverterBoolean());
        registry.addConverter(new ConverterInteger());
        registry.addConverter(new ConverterLong());
        registry.addConverter(new ConverterBigDecimal());
        registry.addConverter(new ConverterString());
        registry.addConverter(new ConverterLocalDateTime());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        log.debug("WebConfig addInterceptors");
        try {
            AlphaInterceptor alphaInterceptor = ac.getBean(AlphaInterceptor.class);
            registry.addInterceptor(alphaInterceptor).addPathPatterns("/**");
        } catch (BeansException e) {
            log.error("No ContextInterceptor:" + e.getMessage(), e);
        }
    }

    @Override
    public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/swagger-ui/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/");
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
        log.debug("WebConfig addResourceHandlers");
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        log.debug("WebConfig addCorsMappings");
        registry.addMapping("/**")
                .allowedOriginPatterns(CorsConfiguration.ALL)
                .allowedHeaders(CorsConfiguration.ALL)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(false)
                .exposedHeaders()
                .maxAge(3600L);
    }

    @Override
    public void addViewControllers(@Nullable ViewControllerRegistry registry) {
        log.debug("WebConfig addViewControllers");
    }

    @Override
    public void configureViewResolvers(@Nullable ViewResolverRegistry registry) {
        log.debug("WebConfig configureViewResolvers");
    }

    @Override
    public void addArgumentResolvers(@Nullable List<HandlerMethodArgumentResolver> resolvers) {
        log.debug("WebConfig addArgumentResolvers");
    }

    @Override
    public void addReturnValueHandlers(@Nullable List<HandlerMethodReturnValueHandler> handlers) {
        log.debug("WebConfig addReturnValueHandlers");
    }

    @Override
    public void configureMessageConverters(@Nullable List<HttpMessageConverter<?>> converters) {
        log.debug("WebConfig configureMessageConverters");
        if (converters == null) {
            converters = new ArrayList<>();
        }
        converters.add(0, getStringHttpMessageConverter());
    }

    @Override
    public void extendMessageConverters(@Nullable List<HttpMessageConverter<?>> converters) {
        log.debug("WebConfig extendMessageConverters");
    }

    @Override
    public void configureHandlerExceptionResolvers(@Nullable List<HandlerExceptionResolver> resolvers) {
        log.debug("WebConfig configureHandlerExceptionResolvers");
    }

    @Override
    public void extendHandlerExceptionResolvers(@Nullable List<HandlerExceptionResolver> resolvers) {
        log.debug("WebConfig extendHandlerExceptionResolvers");
    }

    @Override
    public Validator getValidator() {
        log.debug("WebConfig getValidator");
        return null;
    }

    @Override
    public MessageCodesResolver getMessageCodesResolver() {
        log.debug("WebConfig getMessageCodesResolver");
        return null;
    }

}
